/**
 * @file useStateBag
 * @author  fanxl
 */
import {cloneDeep, get, curry, last, isUndefined} from 'lodash-es';
import type {CurriedFunction2, CurriedFunction3} from 'lodash';
import {ref, type UnwrapRef} from 'vue';
import {isNestedString, keyReg, formatKey} from './utils';
import type {AnyObject} from '../interface';

const setNestedObject = (obj: any, nestedString: string, value: any) => {
    const keys = nestedString.split('.');
    const keysLength = keys.length;
    let currentIndex = 0;
    let target = obj;
    while (currentIndex < keysLength - 1) {
        const key = formatKey(keys[currentIndex]);
        if (isUndefined(target[key])) {
            target[key] = keyReg.exec(keys[currentIndex + 1]) ? [] : {};
        }
        target = target[key];
        currentIndex++;
    }
    const formatCurrentKey = formatKey(keys[currentIndex]);
    // set(target, formatCurrentKey, value);
    target[formatCurrentKey] = value;
};

const delNestedObject = (obj: any, nestedString: string) => {
    const keys = nestedString.split('.').map((el) => {
        if (keyReg.exec(el)) {
            const matchResult = keyReg.exec(el) as Array<string>;
            return matchResult[1];
        }
        return el;
    });
    const tmpKeys = cloneDeep(keys);
    tmpKeys.pop();
    const target = get(obj, tmpKeys.join('.'), {});
    const lastKey = last(keys);
    if (lastKey) {
        delete target[lastKey];
    }
};

export const useStateBag = <T extends AnyObject, S>(
    rawInitialState: T
): {
    state: any;
    getState: () => UnwrapRef<T>;
    getFieldState: CurriedFunction2<boolean, string, any>;
    getInitialState: () => T;
    getClonedInitialState: () => T;
    setInitialState: (nextInitialState: T) => void;
    setState: (nextState: UnwrapRef<T>) => void;
    setBatchState: (nextState: T) => void;
    setFieldState: CurriedFunction3<boolean, string, any, void>;
    deleteFieldState: CurriedFunction2<boolean, string, void>;
} => {
    let initialState = cloneDeep(rawInitialState);
    const getInitialState = () => initialState;
    const getClonedInitialState = () => cloneDeep(initialState);
    const setInitialState = (nextInitialState: T) => {
        initialState = nextInitialState;
    };
    const state = ref(getClonedInitialState());
    const getState = () => state.value;
    const setState = (nextState: UnwrapRef<T>) => {
        state.value = nextState;
    };
    const setBatchState = (nextState: T) => Object.assign(state.value, nextState);
    const setFieldState = curry((isShallow: boolean, fieldName: string, nextState: S) => {
        if (fieldName) {
            if (isNestedString(fieldName) && !isShallow) {
                setNestedObject(state.value, fieldName, nextState);
                return;
            }
            // set(state.value, fieldName, nextState);
            state.value[fieldName] = nextState;
        }
    });
    const deleteFieldState = curry((isShallow: boolean, fieldName: string) => {
        if (fieldName) {
            if (isNestedString(fieldName) && !isShallow) {
                delNestedObject(state.value, fieldName);
                return;
            }
            delete state.value[fieldName];
        }
    });
    const getFieldState = curry((isShallow: boolean, fieldName: string) => {
        if (fieldName) {
            if (isShallow) {
                return state.value[fieldName as keyof UnwrapRef<T>];
            }
            return get(state.value, fieldName);
        }
    });

    return {
        state,
        getState,
        getFieldState,
        getInitialState,
        getClonedInitialState,
        setInitialState,
        setState,
        setBatchState,
        setFieldState,
        deleteFieldState
    };
};
